// SPDX-License-Identifier: GPL-2.0
/* smc.c - Driver of LomboTech SMC module
 *
 * Copyright (C) 2016-2018, LomboTech Co.Ltd.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/module.h>
#include <asm/irq.h>
#include <mach/debug.h>
#include "smc.h"
#include "n9v1/smc_csp.h"

static void __iomem *lombo_smc_base;
static u8 answer_to_rst_temp[ATR_MAX_CNT];
//static __attribute((aligned(0x1000))) u8 smc_t0_tx_temp[1024];
//static __attribute((aligned(0x1000))) u8 smc_t0_rx_temp[1024];


#define SMC_DEV_MAX 1
#define smc_suspend	NULL
#define smc_resume	NULL


void __iomem *get_smc_base_addr(void)
{
	void __iomem *base = lombo_smc_base;
	return base;
}

int lombo_smc_detect(void)
{
	return 0;
}

static int lombo_smc_irq_enable(struct device *dev, unsigned int enabled)
{
	return 0;
}


/* smc detect */
void smc_detect(struct platform_device *pdev)
{
	int ret = 0;
	int cycle = 0;
	void __iomem *base = get_smc_base_addr();

	if (ret) {
		csp_smc_set_rst_hold_time(base, cycle);
		csp_smc_set_clk_hold_time(base, cycle);
		csp_smc_set_io_hold_time(base, cycle);
		PRT_INFO("release smc\n");
	} else {
		csp_smc_reset_assert_time(base, cycle);
		csp_smc_set_io_release_timeout_counter(base, cycle);
		csp_smc_set_rst_hold_time(base, cycle);
	}
}

void smc_common_int_process(void)
{
	u32 int_pd_temp;
	void __iomem *base = get_smc_base_addr();
	/* check all error */
	int_pd_temp = csp_smc_check_int_pd(base, (INT_ALL_ERROR |
			INT_FSM_FINISH | INT_ATR_FINISH | INT_TX_EMPTY|
			INT_RX_FULL|INT_RX_BLOCK_PROLOGUE_RECEIVED));

	/* check error */
	if ((int_pd_temp & INT_ALL_ERROR) != 0)
		PRT_INFO("smc_error_pd is: %x\n", int_pd_temp);

#ifdef SMC_FSM_FINISH_INT_EN
	if ((int_pd_temp & INT_FSM_FINISH) != 0) {
		csp_smc_clear_int_pd(base, INT_FSM_FINISH);
		PRT_INFO("csp_smc_fsm_finish_int_trgged\n");
		csp_smc_fsm_finish_int_flag = 1;	/* set int flag */
	}
#endif
}

void smc_irq_handle(void)
{
	smc_common_int_process();
}

/* smc data-exchange
 * smc interface data transfer must be initiated by smc and rsp-ed by card
 */
int smc_t0_data_exchange(u64 tx_buf, u32 tx_cnt, u64 rx_buf, struct smc_rx *rx)
{
	u32 ret = 0;
	u32 int_pd_temp = 0;

	u8 *tx_p, *rx_p;
	u32 i, j;
	void __iomem *base = get_smc_base_addr();

	/* clear all int */
	csp_smc_clear_int_pd(base, 0xffffffff);
	/* special mode sel */
	csp_smc_sel_special_mode(base, SPECIAL_MODE_NORMAL);
	/* t=0 mode */
	csp_smc_sel_t0_t1_mode(base, TRANSFER_MODE_T0);
	/* set tx len */
	csp_smc_set_tx_len(base, tx_cnt);
	/* tx-start */
	csp_smc_sel_start(base, 1);

	/* tx start
	 * there is no tx-len reg cfg in t0 mode
	 * the sw should know how much data should be senm d
	 */

	j = 0;
	tx_p = (u8 *)tx_buf;
	for (i = 0; i < tx_cnt;) {
		if (csp_smc_is_tx_fifo_full(base) == 0) {
			csp_smc_write_data(base, *tx_p);
			tx_p++;
			i++;
		}

		/* quit case */
		int_pd_temp = csp_smc_check_int_pd(base,
				(INT_ALL_ERROR | INT_CWT_TO | INT_ATR_FINISH));
		if (int_pd_temp & INT_ALL_ERROR) {
			ret = -1;
			PRT_INFO("smc_err,int_pd is:%x\n", int_pd_temp);
			goto exit;
		}
	}

	PRT_INFO("smc_tx_%d_byte_data_finished\n", i);

	/* rx start
	 * the sw can not know how much data should be rx
	 * so get data until cwt
	 */
	rx_p = (u8 *)rx_buf;
	while (1) {
		/* error, quit */
		int_pd_temp = csp_smc_check_int_pd(base, (INT_ALL_ERROR |
				INT_CWT_TO | INT_BWT_TO | INT_ATR_FINISH));
		if (int_pd_temp & INT_ALL_ERROR) {
			ret = -1;
			PRT_INFO("smc_err, int_pd is:%x\n", int_pd_temp);
			goto exit;
		}
		/* bwt, quit */
		if (int_pd_temp & INT_BWT_TO) {
			ret = -1;
			PRT_INFO("smc_rx_not_start\n");
			PRT_INFO("smc_bwt\n");
			goto exit;
		}
		/* cwt, quit */
		if (int_pd_temp & INT_CWT_TO) {
			ret = 1;
			PRT_INFO("smc_rx_%d_byte_data\n", j);
			PRT_INFO("smc_cwt\n");
			break;
		}
		/* if rx-fifo is not empty, get data */
		if (csp_smc_is_rx_fifo_empty(base) == 0) {
			*rx_p = csp_smc_get_data(base);
			rx_p++;
			j++;
		}
	}

	rx->rx_cnt = j;
	return ret;

exit:
	if (csp_smc_get_tx_fifo_fill_level(base) != 0) {
		PRT_INFO("tx-fifo is not empty after transfer finished\n");
		csp_smc_sel_tx_fifo_reset(base, 1);
	}

	if (csp_smc_get_rx_fifo_fill_level(base) != 0) {
		PRT_INFO("rx-fifo is not empty after transfer finished\n");
		csp_smc_sel_rx_fifo_reset(base, 1);
	}

	rx->rx_cnt = j;
	return ret;
}

int smc_pps_exchange(void)
{
	u8 smc_t0_tx_temp[4];
	u8 smc_t0_rx_temp[256];
	int ret = 0;
	u32 smc_t0_tx_len = 4;
	struct smc_rx rx;

	smc_t0_tx_temp[0] = 0xff;
	smc_t0_tx_temp[1] = 0x10;
	smc_t0_tx_temp[2] = 0x95;
	smc_t0_tx_temp[3] = 0x7a;

	ret = smc_t0_data_exchange((size_t)(&smc_t0_tx_temp), smc_t0_tx_len,
				   (size_t)(&smc_t0_rx_temp), &rx);
	if (ret < 0)
		PRT_INFO("smc pps exchange error\n");
	return ret;
}

int smc_wait_fsm_finished(void)
{
	u32 ret = 0;
	u32 i = 0;
	u32 int_pd_temp;
	void __iomem *base = get_smc_base_addr();

	PRT_INFO("start smc wait fsm finished\n");

#ifdef SMC_FSM_FINISH_INT_EN
	smc_fsm_finish_int_flag = 0;
#endif

	//wait_fms_quit_activity
	while (1) {
#ifdef SMC_FSM_FINISH_INT_EN
		if (csp_smc_fsm_finish_int_flag == 1)
			break;
#else
		int_pd_temp = csp_smc_check_int_pd(base, INT_FSM_FINISH);
		if (int_pd_temp != 0)
			break;
#endif

#ifdef	SMC_SW_TIMEOUT_EN
		if (i > FSM_OPRATE_TO_CNT) {
			ret = 1;
			break;
		}
#endif
		i++;
	}

	return ret;
}

/* stop clock */
int smc_stop_clk(void)
{
	void __iomem *base = get_smc_base_addr();

	csp_smc_sel_stop_clk_en(base, 1);
	csp_smc_sel_stop_clk_polarity(base, 1);
	return 0;
}

/* release all */
int smc_release_all(void)
{
	void __iomem *base = get_smc_base_addr();

	csp_smc_sel_special_mode(base, SPECIAL_MODE_DEACTIVATION);
	return 0;
}

/* select special operation */
int smc_select_special_operation(int mode)
{
	//void __iomem *base = get_smc_base_addr();
	switch (mode) {
	case SPECIAL_MODE_NORMAL:
		smc_normal_mode();
		break;
	case SPECIAL_MODE_ACTIVATION_AND_COLD_RESET:
		smc_activation_and_cold_reset();
		break;
	case SPECIAL_MODE_WARM_RESET:
		smc_warm_reset();
		break;
	case SPECIAL_MODE_DEACTIVATION:
		smc_deactivation_mode();
		break;
	default:
		PRT_INFO("specail mode error, have not this mode");
		return -1;
	}
	return 0;
}

/* select transfer operation, 0 is T0, 1 is T1 */
int smc_select_transfer_operation(int mode)
{
	void __iomem *base = get_smc_base_addr();

	switch (mode) {
	case TRANSFER_MODE_T0:
		csp_smc_sel_t0_t1_mode(base, TRANSFER_MODE_T0);
		break;
	case TRANSFER_MODE_T1:
		csp_smc_sel_t0_t1_mode(base, TRANSFER_MODE_T1);
		break;
	default:
		PRT_INFO("select transfer mode error, have not this mode");
		return -1;
	}
	return 0;
}

/* normal mode */
int smc_normal_mode(void)
{
	u32 ret = 0;
	void __iomem *base = get_smc_base_addr();

	/* clear all int */
	csp_smc_clear_int_pd(base, 0xffffffff);
	/* special mode sel */
	csp_smc_sel_special_mode(base, SPECIAL_MODE_NORMAL);
	/* t=0 mode */
	csp_smc_sel_t0_t1_mode(base, TRANSFER_MODE_T0);
	/* tx-start */
	csp_smc_sel_start(base, 1);

	return ret;
}

/* deactivation mode */
int smc_deactivation_mode(void)
{
	u32 ret = 0;
	void __iomem *base = get_smc_base_addr();

	/* clear all int */
	csp_smc_clear_int_pd(base, 0xffffffff);
	/* special mode sel */
	csp_smc_sel_special_mode(base, SPECIAL_MODE_DEACTIVATION);
	/* t=0 mode */
	csp_smc_sel_t0_t1_mode(base, TRANSFER_MODE_T0);
	/* tx-start */
	csp_smc_sel_start(base, 1);

	return ret;
}

int smc_warm_reset(void)
{
	int ret = 0;
	u32 int_pd_temp = 0;
	u8 *p;
	u32 i;
	void __iomem *base = get_smc_base_addr();

	PRT_INFO("start smc warm reset and atr\n");

	/* clear all int */
	csp_smc_clear_int_pd(base, 0xffffffff);
	/* special mode sel */
	csp_smc_sel_special_mode(base, SPECIAL_MODE_WARM_RESET);
	/* t=0 mode */
	csp_smc_sel_t0_t1_mode(base, TRANSFER_MODE_T0);
	/* tx-start */
	csp_smc_sel_start(base, 1);

	/* get atr
	 * and wait fsm finished,error case,or cwt,or data has finished
	 */
	p = (u8 *)(&(answer_to_rst_temp));
	i = 0;
	while (1) {
		/* quit case */
		int_pd_temp = csp_smc_check_int_pd(base,
				(INT_ALL_ERROR | INT_CWT_TO | INT_ATR_FINISH));

		if (int_pd_temp & INT_ALL_ERROR) {
			ret = -1;
			PRT_INFO("smc_atr_err, int_pd is:%x\n", int_pd_temp);
			break;
		}

		if (int_pd_temp & INT_CWT_TO) {
			PRT_INFO("smc_atr_cwt\n");
			break;
		}

		if (int_pd_temp & INT_ATR_FINISH) {
			PRT_INFO("smc_atr_finish\n");
			break;
		}

		if (i >= ATR_MAX_CNT) {
			PRT_INFO("smc_atr_sw_get_enough_data\n");
			break;
		}

		/* if rx-fifo is not empty, get data */
		if (csp_smc_is_rx_fifo_empty(base) == 0) {
			*p = csp_smc_get_data(base);
			p++;
			i++;
		}
	}

	/* check fifo status */
	if ((csp_smc_get_tx_fifo_fill_level(base) != 0) ||
	    (csp_smc_get_rx_fifo_fill_level(base) != 0)) {
		ret = 1;
		PRT_INFO("fifo is not empty after transfer finished\n");
	}

	return ret;
}

/* activation and answer to reset */
int smc_activation_and_cold_reset(void)
{
	u32 ret = 0;
	u32 int_pd_temp = 0;
	u8 *p;
	u32 i;
	void __iomem *base = get_smc_base_addr();

	PRT_INFO("start smc activation and cold reset\n");

	/* clear all int */
	csp_smc_clear_int_pd(base, 0xffffffff);
	/* special mode sel */
	csp_smc_sel_special_mode(base, SPECIAL_MODE_ACTIVATION_AND_COLD_RESET);
	/* t=0 mode */
	csp_smc_sel_t0_t1_mode(base, TRANSFER_MODE_T0);
	/* tx-start */
	csp_smc_sel_start(base, 1);

	/* get atr
	 * and wait fsm finished, error case, or cwt, or data has finished
	 */
	p = (u8 *)(&(answer_to_rst_temp));
	i = 0;
	while (1) {
		/* quit case */
		int_pd_temp = csp_smc_check_int_pd(base,
				(INT_ALL_ERROR | INT_CWT_TO | INT_ATR_FINISH));
		if (int_pd_temp & INT_ALL_ERROR) {
			ret = 1;
			PRT_INFO("smc_atr_err, int_pd is:%x\n", int_pd_temp);
			break;
		}

		if (int_pd_temp & INT_CWT_TO) {
			ret = 2;
			PRT_INFO("smc_atr_cwt\n");
			break;
		}

		if (int_pd_temp & INT_ATR_FINISH) {
			ret = 3;
			PRT_INFO("smc_atr_finish\n");
			break;
		}

		if (i >= ATR_MAX_CNT) {
			ret = 4;
			PRT_INFO("smc_atr_sw_get_enough_data\n");
			break;
		}

		/* if rx-fifo is not empty, get data */
		if (csp_smc_is_rx_fifo_empty(base) == 0) {
			*p = csp_smc_get_data(base);
			p++;
			i++;
		}
	}

	/* check fifo status */
	if ((csp_smc_get_tx_fifo_fill_level(base) != 0) ||
	    (csp_smc_get_rx_fifo_fill_level(base) != 0)) {
		ret = 1;
		PRT_INFO("fifo is not empty after transfer finished\n");
	}

	return ret;
}

void smc_config_init(void)
{
	void __iomem *base = lombo_smc_base;

	//default baudrate cfg (fi/di = 372)
	csp_smc_set_baud_div(base, ATR_FI_DI_DIV);
	//tx repeat
	csp_smc_set_tx_auto_repeat_en(base, 1);
	csp_smc_set_tx_auto_repeat_cnt(base, 0xf);
	//rx parity error behavior
	csp_smc_sel_rx_parity_error_mode(base, T0_RX_PARITY_ERROR);

	csp_smc_set_block_wait_time(base, BWT_CNT_200MS);

}

int smc_init(void)
{
	void __iomem *base = lombo_smc_base;

	PRT_INFO("start smc init\n");
	csp_smc_set_tx_auto_repeat_en(base, 0);
	smc_warm_reset();
	smc_pps_exchange();
	csp_smc_set_baud_div(base, NORMAL_FI_DI_DIV);

	return 0;
}

void smc_info_test(void)
{
	int ret;
	void __iomem *base = lombo_smc_base;

	PRT_INFO("smc info test\n");

	ret = csp_smc_get_special_mode(base);
	PRT_INFO("smc specialmode is %d\n", ret);
	ret = csp_smc_get_t0_t1_mode(base);
	PRT_INFO("smc transfer mode is %d\n", ret);
	ret = csp_smc_get_stop_clk_en(base);
	PRT_INFO("smc stop clk en is %d\n", ret);
	ret = csp_smc_get_stop_clk_en(base);
	PRT_INFO("smc block edc type is %d\n", ret);
}

static const struct smc_class_ops lombo_smc_ops = {
	.irq_enable		= lombo_smc_irq_enable,
};

static const struct of_device_id lombo_smc_match[] = {
	{ .compatible = "lombo, n9-smc" },
	{},
};
MODULE_DEVICE_TABLE(of, lombo_smc_match);

static int lombo_smc_probe(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node;
	struct lombo_smc *smc = NULL;
	struct resource *res = NULL;
	struct pinctrl *pctrl = NULL;
	struct lombo_smc_clk *smc_clk = NULL;
	int err = 0;
	int ret = 0;

	smc = kzalloc(sizeof(struct lombo_smc), GFP_KERNEL);
	if (!smc) {
		PRT_ERR("failed to malloc memory\n");
		return -ENOMEM;
	}

	/* get IO */
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (res == NULL) {
		dev_err(&pdev->dev, "failed to get memory region resource\n");
		ret = __LINE__;
		goto out;
	}

	/* get IO */
	lombo_smc_base = ioremap(res->start, resource_size(res));
	if (lombo_smc_base == NULL) {
		dev_err(&pdev->dev, "failed ioremap()!\n");
		ret = __LINE__;
		goto out;
	}

	/* get IRQ */
	smc->irq_no = platform_get_irq(pdev, 0);
	if (smc->irq_no < 0) {
		dev_err(&pdev->dev, "no irq for smc!\n");
		ret = __LINE__;
		goto out;
	}

	smc_clk = kzalloc(sizeof(struct lombo_smc_clk), GFP_KERNEL);

	/* get smc apb gate */
	smc_clk->apb_gate = of_clk_get(np, 0);
	if (IS_ERR(smc_clk->apb_gate)) {
		PRT_ERR("smc%d get apb gate clk failed\n", pdev->id);
		return -EINVAL;
	}

	/* enable smc apb gate */
	err = clk_prepare_enable(smc_clk->apb_gate);
	if (err) {
		PRT_ERR("smc%d enable apb gate clk failed\n", pdev->id);
		return err;
	}

	/* get smc apb reset */
	smc_clk->apb_reset = of_clk_get(np, 1);
	if (IS_ERR(smc_clk->apb_reset)) {
		PRT_ERR("smc%d get apb reset clk failed\n", pdev->id);
		return -EINVAL;
	}

	/* enable smc apb reset */
	err = clk_prepare_enable(smc_clk->apb_reset);
	if (err) {
		PRT_ERR("smc%d enable apb reset clk failed\n", pdev->id);
		return err;
	}

	/* get smc module clk */
	smc_clk->smc_clk = of_clk_get(np, 2);
	if (IS_ERR(smc_clk->smc_clk)) {
		PRT_ERR("smc%d get clk failed\n", pdev->id);
		return -EINVAL;
	}

	/* register smc device */
	smc->smc_dev = smc_device_register("n9-smc", &pdev->dev,
				&lombo_smc_ops, THIS_MODULE);
	if (IS_ERR(smc->smc_dev)) {
		dev_err(&pdev->dev, "cannot attach smc\n");
		ret = PTR_ERR(smc->smc_dev);
		goto err_nosmc;
	}

	smc_config_init();
	pctrl = devm_pinctrl_get(&pdev->dev);
	smc_select_gpio_state(pctrl, "default", pdev->id);
	PRT_INFO("smc gpio id %d", pdev->id);
	smc_init();
	smc_info_test();

	PRT_INFO("smc probe end\n");

	return 0;

out:
	return ret;
err_nosmc:
	iounmap(lombo_smc_base);
	return ret;
}

static int lombo_smc_remove(struct platform_device *pdev)
{
	struct lombo_smc *smc = platform_get_drvdata(pdev);

	free_irq(smc->irq_no, smc);
	smc_device_unregister(smc->smc_dev);
	platform_set_drvdata(pdev, NULL);

	iounmap(lombo_smc_base);
	kfree(smc);

	return 0;
}

static struct platform_driver lombo_smc_driver = {
	.probe		= lombo_smc_probe,
	.remove		= lombo_smc_remove,
	.driver		= {
		.name	= "smc",
		.owner	= THIS_MODULE,
		.of_match_table = lombo_smc_match,
	},
};

static void lombo_smc_init(void)
{
	platform_driver_register(&lombo_smc_driver);
}

static void lombo_smc_exit(void)
{
	platform_driver_unregister(&lombo_smc_driver);
}

module_init(lombo_smc_init);
module_exit(lombo_smc_exit);

MODULE_AUTHOR("lomboswer@lombotech.com");
MODULE_DESCRIPTION("Driver for LomboTech SMC module");
MODULE_LICENSE("GPL");
